/**
 * Copyright (C) 2010 altuure <altuure [AT] gmail [DOT] com> http://www.altuure.com/projects/yagdao
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.altuure.yagdao.spring.common;

import com.altuure.yagdao.GenericDAO;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class DAOConfig {
    private static final String DAO = "dao";
    private static final String BASE_PACKAGE = "base-package";
    private static final String BASE_CLASS = "base-class";
    private static final String ID = "id";
    protected Map<String, String> daoMap = new HashMap<String, String>();

    public DAOConfig(Element element, ParserContext parserContext) {
        addDAOConfig(element, parserContext);
        NodeList nodes = element.getChildNodes();

        for (int i = 0; i < nodes.getLength(); i++) {

            Node node = nodes.item(i);

            boolean isElement = Node.ELEMENT_NODE == node.getNodeType();
            boolean isDao = DAO.equals(node.getLocalName()) && element.getNamespaceURI().equals(node.getNamespaceURI());

            if (isElement && isDao) {
                addDAOConfig((Element) node, parserContext);
            }
        }

    }

    private void addDAOConfig(Element element, ParserContext parserContext) {
        String id = null;
        if (element.hasAttribute(ID))
            id = element.getAttribute(ID);

        if (element.hasAttribute(BASE_CLASS)) {
            String attribute = element.getAttribute(BASE_CLASS);
            if (id == null)
                id = attribute;
            daoMap.put(id, attribute);
        } else if (element.hasAttribute(BASE_PACKAGE)) {
            Set<String> findValidClasses = findValidClasses(parserContext, element.getAttribute(BASE_PACKAGE));
            for (String string : findValidClasses) {
                daoMap.put(string, string);
            }
        }
    }

    public synchronized final Map<String, String> getDaoMap() {
        return daoMap;
    }

    private Set<String> findValidClasses(ParserContext parserContext, String basePackage) {
        org.springframework.core.io.ResourceLoader loader = parserContext.getReaderContext().getResourceLoader();

        ClassPathScanningCandidateComponentProvider scanner = new GenericDaoComponentProvider();
        scanner.setResourceLoader(loader);

        Set<BeanDefinition> findCandidateComponents = scanner.findCandidateComponents(basePackage);

        Set<String> interfaceNames = new HashSet<String>();
        for (BeanDefinition definition : findCandidateComponents) {
            interfaceNames.add(definition.getBeanClassName());
        }

        return interfaceNames;
    }

    static class GenericDaoComponentProvider extends ClassPathScanningCandidateComponentProvider {

        public GenericDaoComponentProvider() {

            super(false);
            addIncludeFilter(new InterfaceTypeFilter(GenericDAO.class));

        }

        @Override
        protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {

            boolean isTopLevelType = !beanDefinition.getMetadata().hasEnclosingClass();

            return isTopLevelType;
        }

        private static class InterfaceTypeFilter extends AssignableTypeFilter {

            public InterfaceTypeFilter(Class<?> targetType) {

                super(targetType);
            }

            @Override
            public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
                    throws IOException {

                return metadataReader.getClassMetadata().isInterface()
                        && super.match(metadataReader, metadataReaderFactory);
            }
        }
    }
}
